/*
 * $Id: Socket.cpp 50 2009-02-10 05:48:05Z hyunghwan.chung $
 *
   Copyright 2005-2009 Chung, Hyung-Hwan.

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an  BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
 */

#include <xp/net/Socket.hpp>
#include <xp/net/InetAddress.hpp>
#include <xp/bas/string.h>
#include <xp/bas/memory.h>
#include <xp/bas/locale.h>
#include <xp/bas/errno.h>
#include <xp/bas/assert.h>

#ifndef _WIN32
#include <netinet/in.h>
#include <net/if.h>
#include <netinet/tcp.h>
#endif

namespace xp
{
	namespace net
	{

		int Socket::v6_supported = 0;

		Socket::Socket (int type)
		{

			// NOTICE:	
			//   The socket can't be created in the constructor
			//   as the implementaion is intended to support both 
			//   ipv4 and ipv6 seamlessly. The new name resolution
			//   API's don't effectively allow the creation of socket
			//   before the successful name lookup. So the lazy creation
			//   of sockets is empolyed.
	
			this->socket_type = type;
			this->sckhnd = INVALID_HANDLE;
			this->error_code = ERR_NONE;

			disableTimeout ();
		}

		Socket::~Socket ()
		{
			close ();
		}

		int Socket::startup (bool try_v6)
		{
			xp_sck_t sck;
		   
			if (::xp_sckstartup() == -1) return -1;

			if (try_v6) {
				if (::xp_sck_open (&sck,
					InetAddress::V6, Socket::DATAGRAM, 0) == XP_NULL) {
		#ifdef _WIN32
					if (XP_NET_SOCKET_ERRNO == WSAEAFNOSUPPORT) {
		#else
					if (XP_NET_SOCKET_ERRNO == EAFNOSUPPORT || 
					    XP_NET_SOCKET_ERRNO == ENOSYS)  {
		#endif
						v6_supported = -1;
					}
				}
				else {
					::xp_sck_close (&sck);
					v6_supported = 1;
				}
			}
			else v6_supported = -1;

			return 0;
		}

		int Socket::cleanup ()
		{
			return ::xp_sckcleanup ();
		}

		int Socket::open (Handle handle)
		{
			XP_NET_SOCKET_CHECK_OPEN_HANDLE(this->sckhnd);

			int tmp;
			len_t len = XP_SIZEOF(tmp);

			if (xp_sck_openhnd(this, handle) == XP_NULL) return -1;

			if (xp_sck_getopt (this, 
				SOL_SOCKET, SO_TYPE, (char*)&tmp, &len) == -1) {
				this->error_code = ERR_TYPE_RETRIEVAL;
				xp_sck_forfeit (this);
				this->sckhnd = INVALID_HANDLE;
				return -1;
			}

			if (this->socket_type != 0 && 
			    this->socket_type != tmp) {
				xp_sck_forfeit (this);
				this->error_code  = ERR_TYPE_MISMATCH;
				return -1;
			}

			this->socket_type = tmp;
			return 0;
		}

		int Socket::close ()
		{
			XP_NET_SOCKET_CHECK_CLOSED_HANDLE(this->sckhnd);
		
			int n = ::xp_sck_close (this);
			if (n == -1) {
				this->error_code = translate_errno (
					XP_NET_SOCKET_ERRNO, ERR_GENERAL);
			}
			else this->sckhnd = INVALID_HANDLE;

			return n;
		}

		int Socket::shutdown (int how)
		{
			XP_NET_SOCKET_CHECK_CLOSED_HANDLE(this->sckhnd);

			int n = ::xp_sck_shtdwn (this, how);
			if (n == -1) {
				this->error_code = translate_errno (
					XP_NET_SOCKET_ERRNO, ERR_GENERAL);
			}
			return n;
		}

		xp_ssize_t Socket::send (
			const void* data, xp_size_t size, int flags) 
		{
			XP_NET_SOCKET_CHECK_CLOSED_HANDLE(this->sckhnd);
			xp_ssize_t n = ::xp_sck_send (this, data, size, flags);
			if (n == -1) {
				this->error_code = translate_errno (
					XP_NET_SOCKET_ERRNO, ERR_GENERAL);
			}
			return n;
		}

		xp_ssize_t Socket::sendAll (const void* data, xp_size_t size, int flags) 
		{
			XP_NET_SOCKET_CHECK_CLOSED_HANDLE(this->sckhnd);
			xp_ssize_t n = ::xp_sck_sendall (this, data, size, flags);
			if (n == -1) {
				this->error_code = translate_errno (
					XP_NET_SOCKET_ERRNO, ERR_GENERAL);
			}
			return n;
		}

		xp_ssize_t Socket::receive (void* buf, xp_size_t size, int flags) 
		{
			XP_NET_SOCKET_CHECK_CLOSED_HANDLE(this->sckhnd);

			xp_ssize_t n = ::xp_sck_recv (this, buf, size, flags);
			if (n == -1) {
				this->error_code = translate_errno (
					XP_NET_SOCKET_ERRNO, ERR_GENERAL);
			}
			return n;
		}

		xp_ssize_t Socket::receiveAll (void* buf, xp_size_t size, int flags) 
		{
			XP_NET_SOCKET_CHECK_CLOSED_HANDLE(this->sckhnd);

			xp_ssize_t n = ::xp_sck_recvall (this, buf, size, flags);
			if (n == -1) {
				this->error_code = translate_errno (
					XP_NET_SOCKET_ERRNO, ERR_GENERAL);
			}
			return n;
		}

		int Socket::getOption (
			int level, int optname, void* optval, len_t* optlen)
		{
			XP_NET_SOCKET_CHECK_CLOSED_HANDLE(this->sckhnd);
			int n = ::xp_sck_getopt (
				this, level, optname, (char*)optval, optlen);
			if (n == -1) {
				this->error_code = translate_errno (
					XP_NET_SOCKET_ERRNO, ERR_SOCKOPT);
			}
			return n;
		}

		int Socket::setOption (
			int level, int optname, const void* optval, len_t optlen)
		{
			XP_NET_SOCKET_CHECK_CLOSED_HANDLE(this->sckhnd);
			int n = ::xp_sck_setopt (
				this, level, optname, (const char*)optval, optlen);
			if (n == -1) {
				this->error_code = translate_errno (
					XP_NET_SOCKET_ERRNO, ERR_SOCKOPT);
			}
			return n;
		}

		int Socket::setDebug (int n) 
		{
			return setOption (
				SOL_SOCKET, SO_DEBUG, (char*)&n, XP_SIZEOF(n));
		};

		int Socket::setReuseAddr (int n) 
		{
			return setOption (
				SOL_SOCKET, SO_REUSEADDR, (char*)&n, XP_SIZEOF(n));
		}

		int Socket::setKeepAlive (int n, int keepidle, int keepintvl, int keepcnt) 
		{
			if (setOption (SOL_SOCKET, SO_KEEPALIVE, (char*)&n, XP_SIZEOF(n)) <= -1) return -1;

			// the following values are just hints. 
			// i don't care about success and failure
		#if defined(TCP_KEEPIDLE) && defined(SOL_TCP)
			if (keepidle > 0)
				setOption (SOL_TCP, TCP_KEEPIDLE, (char*)&keepidle, XP_SIZEOF(keepidle));
		#endif
		#if defined(TCP_KEEPINTVL) && defined(SOL_TCP)
			if (keepintvl > 0)
				setOption (SOL_TCP, TCP_KEEPINTVL, (char*)&keepintvl, XP_SIZEOF(keepintvl));
		#endif
		#if defined(TCP_KEEPCNT) && defined(SOL_TCP)
			if (keepcnt > 0)
				setOption (SOL_TCP, TCP_KEEPCNT, (char*)&keepcnt, XP_SIZEOF(keepcnt));
		#endif
			return 0;
		}


		int Socket::setBroadcast (int n) 
		{
			return setOption (
				SOL_SOCKET, SO_BROADCAST, (char*)&n, XP_SIZEOF(n));
		}
		
		int Socket::setSendBuf (unsigned int size) 
		{
			return setOption (
				SOL_SOCKET, SO_SNDBUF, (char*)&size, XP_SIZEOF(size));
		}

		int Socket::setRecvBuf (unsigned int size) 
		{
			return setOption (
				SOL_SOCKET, SO_RCVBUF, (char*)&size, XP_SIZEOF(size));
		}
		
		int Socket::setLingerOn (int sec)
		{
			struct linger lng;
			lng.l_onoff = 1;
			lng.l_linger = sec;
			return setOption (
				SOL_SOCKET, SO_LINGER, (char*)&lng, XP_SIZEOF(lng));
		}

		int Socket::setLingerOff ()
		{
			struct linger lng;
			lng.l_onoff = 0;
			lng.l_linger = 0;
			return setOption (SOL_SOCKET, 
				SO_LINGER, (char*)&lng, XP_SIZEOF(lng));
		}
		
		int Socket::setTcpNodelay (int n) 
		{
		#ifdef TCP_NODELAY
			return setOption (IPPROTO_TCP, 
				TCP_NODELAY, (char*)&n, XP_SIZEOF(n));
		#else
			this->error_code = ERR_NOSUPPORT;
			return -1;
		#endif
		}

		int Socket::setOobInline (int n)
		{
		#ifdef SO_OOBINLINE
			return setOption (SOL_SOCKET, 	
				SO_OOBINLINE, (char*)&n, XP_SIZEOF(n));
		#else
			this->error_code = ERR_NOSUPPORT;
			return -1;
		#endif
		}

		int Socket::setIpv6Only (int n)
		{
		#ifdef IPV6_V6ONLY
			return setOption (IPPROTO_IPV6, 
				IPV6_V6ONLY, (char*)&n, XP_SIZEOF(n));
		#else
			this->error_code = ERR_NOSUPPORT;
			return -1;
		#endif
		}	

		int Socket::bindToDevice (const xp_char_t* device)
		{
		#ifdef SO_BINDTODEVICE
			if (device == XP_NULL) {
				return setOption (SOL_SOCKET, 
					SO_BINDTODEVICE, XP_NULL, 0);
			}
			else {
				struct ifreq iface;
				xp_memset (&iface, 0, XP_SIZEOF(iface));

		#ifdef XP_CHAR_IS_MCHAR
				xp_strxcpy (iface.ifr_name, xp_countof(iface.ifr_name), device);
		#else
				if (xp_wcstomcs(device, iface.ifr_name,
					xp_countof(iface.ifr_name)) < xp_strlen(device)) {
					this->error_code = ERR_INVALID;
					return -1;
				};
		#endif
				return setOption (SOL_SOCKET, 
					SO_BINDTODEVICE, (char*)&iface, XP_SIZEOF(iface));
			}
		#else
			this->error_code = ERR_NOSUPPORT;
			return -1;
		#endif
		}

		int Socket::unbindToDevice ()
		{
			return bindToDevice (XP_NULL);
		}

		int Socket::setNonblock (bool nonblock)
		{
			return ::xp_scksetnonblock (this->handle(), nonblock);
		}


		int Socket::create_socket (int domain, int type, int proto)
		{
			XP_ASSERT (this->sckhnd == INVALID_HANDLE);

			// domain adjustment
			if (domain == InetAddress::V0 && 
			    v6_supported == -1) domain = InetAddress::V4;

			if (::xp_sck_open(this, domain, type, proto) == XP_NULL) {
				this->error_code = translate_errno (
					XP_NET_SOCKET_ERRNO, ERR_CREATE);
				return -1;
			}

			return 0;
		}

		int Socket::bind_socket (addr_t* addr, len_t len)
		{
			XP_ASSERT (this->sckhnd != INVALID_HANDLE);

			int n = ::xp_sck_bind (this, addr, len);
			if (n == -1) {
				this->error_code = translate_errno (
					XP_NET_SOCKET_ERRNO, ERR_GENERAL);
			}
			return n;
		}

		int Socket::connect_socket (addr_t* addr, len_t len)
		{
			XP_ASSERT (this->sckhnd != INVALID_HANDLE);

			xp_sck_setcancel (this, XP_SCK_CONN, NULL, NULL);
			int n = ::xp_sck_conn (this, addr, len);
			if (n == -1) {
				this->error_code = translate_errno (
					XP_NET_SOCKET_ERRNO, ERR_GENERAL);
			}
			return n;
		}

		int Socket::connect_socket (addr_t* addr, len_t len, xp_bool_t (*cancel) (void*))
		{
			XP_ASSERT (this->sckhnd != INVALID_HANDLE);

			xp_sck_setcancel (this, XP_SCK_CONN, cancel, this);
			int n = ::xp_sck_conn (this, addr, len);
			if (n == -1) {
				this->error_code = translate_errno (
					XP_NET_SOCKET_ERRNO, ERR_GENERAL);
			}
			return n;
		}

		int Socket::init_connect_socket (addr_t* addr, len_t len)
		{
			XP_ASSERT (this->sckhnd != INVALID_HANDLE);
			int n = ::xp_sck_initconn (this, addr, len);
			if (n == -1) {
				this->error_code = translate_errno (
					XP_NET_SOCKET_ERRNO, ERR_GENERAL);
			}
			return n;
		}

		int Socket::fini_connect_socket ()
		{
			XP_ASSERT (this->sckhnd != INVALID_HANDLE);
			int n = ::xp_sck_finiconn (this);
			if (n == -1) {
				this->error_code = translate_errno (
					XP_NET_SOCKET_ERRNO, ERR_GENERAL);
			}
			return n;
		}

		int Socket::listen_socket (int backlog)
		{
			XP_ASSERT (this->sckhnd != INVALID_HANDLE);

			int n = ::xp_sck_lstn (this, backlog);
			if (n == -1) {
				this->error_code = translate_errno (
					XP_NET_SOCKET_ERRNO, ERR_GENERAL);
			}
			return n;
		}

		int Socket::translate_errno (int num, int def_err)
		{
		/*#ifdef _WIN32
			if (num == WSAETIMEDOUT) return ERR_TIMEDOUT;
			if (num == WSAECANCELED) return ERR_CANCELED;
			if (num == WSAEINVAL || num == WSAENOTSOCK) 
				return ERR_INVALID;
			if (num == WSAEAFNOSUPPORT || num == WSAEPROTONOSUPPORT) 
				return ERR_NOSUPPORT;
			if (num == WSAENOBUFS || num == WSAEMFILE)
				return ERR_NORESOURCE;
			if (num == WSAEACCES) return ERR_ACCESS;
			if (num == WSAEADDRINUSE) return ERR_ADDRINUSE;
			if (num == WSAECONNREFUSED) return ERR_REFUSED;
			if (num == WSAENOTCONN) return ERR_NOTCONNECTED;
			if (num == WSAENETUNREACH) return ERR_UNREACHABLE;
			return def_err;
		#else*/
			switch (num)
			{
				case XP_ETIMEDOUT:
					return ERR_TIMEDOUT;
			
	
				case XP_ECANCELED: 
					return ERR_CANCELED;

				case XP_EBADF:
				case XP_EINVAL:
				case XP_ENOTSOCK:
					return ERR_INVALID;

				case XP_EAFNOSUPPORT:
				case XP_EPROTONOSUPPORT:
					return ERR_NOSUPPORT;

				case XP_ENOMEM:
				case XP_EMFILE:

					return ERR_NORESOURCE;

				case XP_EWOULDBLOCK:
				//case XP_EAGAIN:
					return ERR_WOULDBLOCK;

				case XP_EACCES:
					return ERR_ACCESS;

				case XP_EADDRINUSE:
					return ERR_ADDRINUSE;

				case XP_ECONNREFUSED:
					return ERR_REFUSED;

				case XP_ENOTCONN:
					return ERR_NOTCONNECTED;

				case XP_ENETUNREACH:
					return ERR_UNREACHABLE;

				case XP_EINPROGRESS: 
					return ERR_INPROGRESS;

				default:
					return def_err;
			}
		/*#endif*/

		}

	}
}
